﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using Hvam.SharePoint.Search.Language;
using Hvam.SharePoint.Search.Language.Visitors;
using Microsoft.Office.Server.Search.Query;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace Hvam.SharePoint.Search.Query.Site.Models
{
    internal class SiteSearchQuery : IDisposable
    {
        // Public properties
        public string Query { get; private set; }

        public List<string> RequestedProperties { get; set; }

        public SortCollection SortList { get; set; }

        public int StartItem { get; set; }

        public int ItemsPerPage { get; set; }

        // Generated properties
        public Expression Expression { get; private set; }

        public Dictionary<string, SPField> AvailableFields { get; set; }

        public Dictionary<string, SPField> RequestedFields { get; private set; }

        public string SiteRestriction { get; set; }

        public SPSite Site { get; private set; }

        public SPWeb Web { get; private set; }

        public SPList List { get; private set; }
        
        // Private properties
        private bool DisposeWeb { get; set; }

        private bool DisposeSite { get; set; }

        public SiteSearchQuery(string query)
        {
            Query = query;

            RequestedProperties = new List<string>();
            SortList = new SortCollection();

            Initialize();
        }

        public SiteSearchQuery(string query, Location location)
        {
            Query = query;

            RequestedProperties = location.RequestedProperties.Cast<string>().Distinct().ToList().ConvertAll(s => s.ToLower());
            SortList = location.GetSortList();
            StartItem = location.StartItem;
            ItemsPerPage = location.ItemsPerPage;

            Initialize();
        }

        private void Initialize()
        {
            // Prase query
            Expression = KeywordQueryParser.Parse(Query);
   
            // Extract site
            var propertyVisitor = new PropertyExtractorExpressionVisitor();
            var tuple = propertyVisitor.Extract(Expression, "site");
            Expression = tuple.Item1;

            switch (tuple.Item2.Count)
            {
                case 0:
                    // Relative
                    if (SPContext.Current == null)
                    {
                        if (SPFarm.Local.CurrentUserIsAdministrator())
                        {
                            Web = SPAdministrationWebApplication.Local.Sites["/"].RootWeb;
                        } 
                        else
                        {
                            throw new Exception("SPContext null");
                        }
                    }
                    else
                    {
                        Web = SPContext.Current.Web;   
                    }

                    DisposeWeb = false;
                    DisposeSite = false;

                    break;
                case 1:
                    // Explicit
                    SiteRestriction = tuple.Item2.First();
                    if (IsUri(SiteRestriction))
                    {
                        Site = new SPSite(SiteRestriction);
                        Web = Site.OpenWeb();

                        DisposeWeb = true;
                        DisposeSite = true;

                        // List
                        var uri = new Uri(SiteRestriction);

                        try
                        {
                            List = Web.GetList(uri.AbsolutePath);
                        }
                        catch (FileNotFoundException e)
                        {
                            throw new Exception("Folder not found.", e);
                        }
                    }
                    else
                    {
                        throw new Exception("Invalid site restrictions.");
                    }
                    break;
                default:
                    throw new Exception("Invalid number for site restrictions.");
            }

            // Extract fields
            AvailableFields = new Dictionary<string, SPField>(StringComparer.OrdinalIgnoreCase);
            if (List == null)
            {
                // From web
                foreach (var field in Web.AvailableFields.OfType<SPField>())
                {
                    AvailableFields.Add(field.InternalName, field);
                }
            }
            else
            {
                // From list
                foreach (var field in List.Fields.OfType<SPField>())
                {
                    AvailableFields.Add(field.InternalName, field);
                }
            }

            var requestedFields = 
                from property in RequestedProperties
                where AvailableFields.ContainsKey(property)
                select AvailableFields[property];

            RequestedFields = new Dictionary<string, SPField>(StringComparer.OrdinalIgnoreCase);
            foreach (var field in requestedFields)
            {
                RequestedFields.Add(field.InternalName, field);
            }

            // Handle owstaxId prefixes separately
            var taxIdFields = RequestedProperties.Where(s => s.StartsWith("owstaxId", StringComparison.OrdinalIgnoreCase));

            foreach (var taxIdField in taxIdFields)
            {
                var fieldName = taxIdField.Substring(8);
                if (AvailableFields.ContainsKey(fieldName) && !RequestedFields.ContainsKey(fieldName))
                {
                    var field = AvailableFields[fieldName];
                    RequestedFields.Add(fieldName, field);
                }
            }
        }

        public void Dispose()
        {
            if (DisposeWeb)
            {
                Web.Dispose();
            }

            if (DisposeSite)
            {
                Site.Dispose();
            }
        }

        public static bool IsUri(string uri)
        {
            try
            {
                new Uri(uri);
                return true;
            }
            catch (UriFormatException)
            {
                return false;
            }
        }
    }
}
